<script lang="ts" setup>
import 'tinymce/skins/ui/oxide/skin.css';
import 'tinymce/skins/ui/oxide-dark/skin.css';

import type { Editor, RawEditorOptions, Events, EditorEvent } from 'tinymce';

import 'tinymce/tinymce';
import 'tinymce/icons/default';
import 'tinymce/themes/silver';
import 'tinymce/models/dom';
import 'tinymce-i18n/langs7/zh_CN.js';

import 'tinymce/plugins/code';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/image';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/link';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/media';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/table';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/emoticons/';
import 'tinymce/plugins/emoticons/js/emojis';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/wordcount';
import 'tinymce/plugins/autosave';
import 'tinymce/plugins/codesample';
import './plugin-markdown-it';

import { computed, ref, watch } from 'vue';
import TinymceEditor from '@tinymce/tinymce-vue';
import { useFormItem } from '@arco-design/web-vue';
import prettyBytes from 'pretty-bytes';
import { useAppStore } from '@/settings/stores';
import useUploader from '@/settings/hooks/uploader';
import { seq, replaceAsync } from './replace';
import { uploadImage } from './upload';
import { useI18n } from 'vue-i18n';
import Prism from 'virtual:prismjs';

import contentUiSkinCss from 'tinymce/skins/ui/oxide/content.css?inline';
import contentUiSkinDarkCss from 'tinymce/skins/ui/oxide-dark/content.css?inline';
import contentCss from 'tinymce/skins/content/default/content.css?inline';
import contentDarkCss from 'tinymce/skins/content/dark/content.css?inline';

export interface BlobInfo {
  id: () => string;
  name: () => string;
  filename: () => string;
  blob: () => Blob;
  base64: () => string;
  blobUri: () => string;
  uri: () => string | undefined;
}
export type ProgressFn = (percent: number) => void;
export type PastePreProcessFn = (editor: Editor, args: Events.PastePreProcessEvent) => void;

defineOptions({
  name: 'OpenRichEditor',
});

window.Prism = Prism;

const { locale, t } = useI18n();
const appStore = useAppStore();
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
  modelValue: {
    type: String,
    default: '',
  },

  height: {
    type: Number,
    default: 500,
  },

  maxFileSize: {
    type: Number,
    default: 1024 * 1024 * 10,
  },
});
const { mergedDisabled, eventHandlers } = useFormItem();
const loading = ref(false);
const { uploadFile } = useUploader();
const value = computed({
  get() {
    return props.modelValue;
  },

  set(value) {
    emit('update:modelValue', value);
    eventHandlers.value?.onChange?.();
  },
});

const imageUploadHandler = async (blobInfo: BlobInfo, progress: ProgressFn) => {
  if (blobInfo.blob().size > props.maxFileSize) {
    throw new Error(t('settings.notification.fileLarge', { size: prettyBytes(props.maxFileSize) }));
  }

  const { isSuccess, url, msg } = await uploadFile(new File([blobInfo.blob()], blobInfo.filename()), {
    onProgress: (progressValue) => {
      progress(progressValue);
    },
  });

  if (!isSuccess) {
    throw new Error(msg);
  }

  return url ?? '';
};

const reImage = /<img(?:\s+?.+?\s+?|\s+?)src\s*?=\s*?"(.+?)"(?:\s+?.+?\s*?|\s*?|\s*\/)>/g;

const uploadImageAndReplaceUrl = async (content: string) => await replaceAsync(content, reImage, seq(async (_: string, p1: string) => {
  const url = await uploadImage(p1);

  return `<img src="${url}">`;
}));

const pastePreprocess = async (editor: Editor, args: EditorEvent<Events.PastePreProcessEvent>) => {
  args.preventDefault();

  editor.mode.set('readonly');
  loading.value = true;

  // 处理微信公众号里的情况
  let content = args.content = args.content.replace(/<section(?:\s+[^>]*?)*>/ig, '<div>').replace(/<\/section>/ig, '</div>');

  content = await uploadImageAndReplaceUrl(args.content);

  editor.mode.set('design');
  loading.value = false;

  editor.insertContent(content)
};

const initOptions = ref<RawEditorOptions>({
  language: locale.value === 'en-US' ? 'en' : 'zh_CN',
  width: '100%',
  height: props.height,
  skin: false,
  promotion: false,
  // menubar: false,
  content_css: false,
  content_style: `
    ${contentUiSkinCss.toString()}
    ${contentUiSkinDarkCss.toString()}
    ${contentCss.toString()}
    ${contentDarkCss.toString()}
  `,
  menu: {
    insert: { title: 'Insert', items: 'image link media inserttable | emoticons hr | insertdatetime | markdownit codesample' },
  },
  removed_menuitems: 'newdocument fontfamily codeformat',
  plugins: [
    'autolink', 'code', 'fullscreen',
    'image', 'insertdatetime', 'link', 'lists', 'media',
    'preview', 'table', 'visualblocks', 'emoticons', 'searchreplace', 'wordcount', 'markdownit', 'autosave',
    'codesample',
  ],
  toolbar: [
    'styles bold italic underline | alignleft aligncenter alignright alignjustify | subscript superscript | forecolor backcolor bullist numlist link unlink image table | restoredraft fullscreen cut copy paste undo redo removeformat | markdownit codesample',
  ],
  contextmenu: 'link linkchecker image table spellchecker configurepermanentpen',
  fullscreen_native: true,
  images_upload_handler: imageUploadHandler,
  images_file_types: 'jpeg,jpg,png,gif,webp,svg',
  paste_tab_spaces: 2,
  branding: false,
  invalid_elements: 'input,textarea',
  paste_preprocess: pastePreprocess as unknown as PastePreProcessFn,
  font_size_input_default_unit: 'px',
  font_size_formats: '16px 18px 20px 22px 24px 36px 48px',
  codesample_global_prismjs: true,
  codesample_languages: [
    { text: 'HTML/XML', value: 'markup' },
    { text: 'JavaScript', value: 'javascript' },
    { text: 'CSS', value: 'css' },
    { text: 'PHP', value: 'php' },
    { text: 'Ruby', value: 'ruby' },
    { text: 'Python', value: 'python' },
    { text: 'Java', value: 'java' },
    { text: 'C', value: 'c' },
    { text: 'C#', value: 'csharp' },
    { text: 'C++', value: 'cpp' },
    { text: 'Bash', value: 'bash' },
    { text: 'Rust', value: 'rust' },
  ],
});

const instance = ref();

const onInit = () => {
  instance.value.getEditor().getDoc().querySelector('html').setAttribute('arco-theme', appStore.theme);
}

watch(() => appStore.theme, (value) => {
  if (instance.value) {
    instance.value.getEditor().getDoc()?.querySelector('html').setAttribute('arco-theme', value);
  }
});
</script>

<template>
  <div class="open-rich-editor mt-5px w-full">
    <ASpin :loading="loading" :tip="$t('settings.component.richEditor.loading')" class="h-full w-full">
      <TinymceEditor license-key="gpl" :init="initOptions" :disabled="mergedDisabled" v-model="value" @init="onInit" ref="instance" />
    </ASpin>
  </div>
</template>

<style lang="scss" scoped>
</style>
